課程:JavaScript 與 React 底層原理 第 9 堂:ES6+ 語法收尾
28 安全存取語法
身為 JavaScript 開發者,你一定遇過這個堪稱「百萬美元錯誤」的紅字報錯:TypeError: Cannot read property 'xxx' of undefined。當你的程式碼試圖從一個不存在的物件中讀取屬性時,整個應用程式會直接崩潰。在過去,我們被迫寫出長長的邏輯門檻來保護程式碼,但現代 JavaScript 提供了更優雅、更安全的「防護罩」。
消失的鏈結:Optional Chaining (?. )
想像你正在開發一個電子商務網站,你需要從 API 獲取使用者資料並顯示他們居住的城市。資料結構可能長這樣:
const user = {
id: 1,
profile: {
name: "Aria",
address: {
city: "台北",
street: "忠孝東路"
}
}
};
要在畫面上顯示城市,你可能會直覺地寫下 user.profile.address.city。但現實總是殘酷的,API 回傳的資料可能因為使用者沒填寫資料、或是資料載入中,導致 profile 或 address 是 null 或 undefined。
傳統的「邏輯門神」模式
在 ES2020 之前,為了防止程式碼崩潰,我們必須使用 && 運算子進行層層檢查:
// 傳統寫法:冗長且難以閱讀
const city = user && user.profile && user.profile.address && user.profile.address.city;
console.log(city); // "台北" 或 undefined
這種寫法本質上是利用了 && 的特性:如果左側為 Falsy,就停止往右執行。雖然有效,但當層級變深時,程式碼會變得像「火車軌道」一樣冗長,閱讀體驗極差。
預測:如果我們中間斷掉會怎樣?
如果 user.profile 是 null,而你依然執行 user.profile.address,JavaScript 引擎會發出抗議。現在,讓我們看看 Optional Chaining(選擇性鏈結) 如何優雅地解決這個問題。
// 現代寫法:簡潔有力
const city = user?.profile?.address?.city;
console.log(city);
短路求值(Short-circuiting)的原理
?. 的核心機制在於「短路」。當 JavaScript 執行到 ?. 時,它會檢查左側的值:
- 如果左側是
null或undefined,運算會立即停止,並直接回傳undefined。 - 如果左側有值(不論是什麼,只要不是空值),則繼續往右讀取屬性。
這就像是在每一層存取之前都放了一個自動感應門,如果沒人(空值),門就不會打開,後面的程式碼也就不會被執行,成功避免了噴錯。
不只是讀取屬性:函數與陣列
Optional Chaining 的威力不僅限於物件屬性,它還能處理更複雜的場景:
1. 安全的函數呼叫
在 React 中,我們常會透過 Props 傳遞回調函數(Callback)。如果父元件忘記傳入該函數,直接呼叫會導致崩潰。
// 如果 props.onSuccess 存在就執行,不存在就不執行且不報錯
props.onSuccess?.(data);
2. 安全的陣列存取
當你不確定一個陣列是否存在,或是該索引位置是否有值時:
const firstItem = data?.list?.[0];
這裡要注意語法是 ?.[],這在處理動態 Key 值或陣列時非常有用。
專家提醒:不要過度依賴 ?.
雖然 ?. 很方便,但請記住它是一個「靜音器」。如果你在所有地方都加上 ?.,可能會掩蓋掉原本應該被發現的 Bug。
- 建議: 只在你「預期」資料可能是空值的地方使用(例如:API 回傳值、選填的 Props)。
- 警告: 絕對不要在賦值左側使用,例如
user?.name = "Bob"是語法錯誤。
聰明的預設值:Nullish Coalescing (??)
當我們使用 ?. 拿到了 undefined 之後,下一步通常是:「如果拿不到資料,我該顯示什麼預設值?」
|| (OR) 的陷阱
長久以來,JavaScript 開發者習慣使用 || 來設定預設值:
const settings = {
speed: 0,
title: ""
};
const currentSpeed = settings.speed || 10;
const currentTitle = settings.title || "預設標題";
console.log(currentSpeed); // 預測:0? 現實:10 (喔不!)
console.log(currentTitle); // 預測:""? 現實:"預設標題"
為什麼會這樣?因為 || 的邏輯是:只要左邊是「Falsy」值,就回傳右邊。
在 JavaScript 中,0、""、false、NaN 全部都是 Falsy。這在處理數值計算或字串輸入時,會造成嚴重的邏輯錯誤——使用者明明輸入了 0(這是一個有效的速度),系統卻把它當成沒有輸入,硬塞給他 10。
救星登場:Nullish Coalescing (??)
Nullish Coalescing(空值合併運算子) 的出現,就是為了修正 || 的這個「過度熱心」的行為。
?? 只有在左側為 Nullish(僅限 null 或 undefined)時,才會回傳右側的值。
const currentSpeed = settings.speed ?? 10;
const currentTitle = settings.title ?? "預設標題";
console.log(currentSpeed); // 0 (正確!)
console.log(currentTitle); // "" (正確!)
為什麼這對 React 很重要?
在 React 中,狀態(State)初始化常會用到這個技巧。假設你有一個計數器元件:
function Counter({ initialCount }) {
// 如果 initialCount 是 0,使用 || 會變成 10,這是不正確的
const [count, setCount] = useState(initialCount ?? 10);
return <div>{count}</div>;
}
使用 ?? 可以確保當父元件傳入 0 時,計數器能正確從 0 開始,而不是跳到預設值。
強強聯手:建立安全資料管線
在實戰中,我們通常會將這兩個語法組合起來,形成一條堅不可摧的資料存取鏈結。這在處理後端 API 的深層巢狀資料時尤其強大。
實戰場景:顯示使用者地點
假設我們正在處理一個複雜的 API 回應,我們想要顯示城市名稱,若沒有資料則顯示「未知城市」。
const apiResponse = {
data: {
user: {
profile: null // 使用者還沒填寫個人資料
}
}
};
// 組合應用
const cityName = apiResponse?.data?.user?.profile?.address?.city ?? "未知城市";
console.log(cityName); // "未知城市"
這行程式碼的執行邏輯如下:
apiResponse?.data:存在。?.user:存在。?.profile:是null。- 短路發生: 因為遇到
null,之後的?.address?.city全部被跳過,整個左側表達式回傳undefined。 undefined ?? "未知城市":因為左側是undefined,??運算子發揮作用,回傳右側的 "未知城市"。
整個過程沒有拋出任何 Error,且邏輯清晰易讀。
在 React 中的防禦性編程模式
身為一名資深的 React 開發者,你會發現這兩個語法幾乎無處不在。以下是幾個標準的應用模版:
1. 防止 Loading 狀態崩潰
當資料還在 Fetch 時,data 通常是 null。如果你直接 .map,頁面會白屏。
function UserList({ data }) {
// data 可能還是 null,這裡使用 ?? 提供空陣列作為後路
const list = data?.results ?? [];
return (
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
2. 選填配置項的預設值
在封裝 UI 組件(如 Modal 或 Button)時,我們會有很多可選的設定。
const Modal = ({ config }) => {
const isClosable = config?.allowClose ?? true; // 預設可以關閉
const themeColor = config?.style?.color ?? "#333";
// ...
};
3. API 錯誤處理的簡化
有時候 API 出錯會回傳一個包含錯誤訊息的物件,有時候則是空的。
const errorMessage = response?.error?.message ?? "發生未知錯誤,請稍後再試";
進階細節:語法限制與優先順序
在使用這些語法時,有兩個小陷阱需要注意:
1. 混合使用時的括號限制
出於安全考慮,JavaScript 不允許在沒有括號的情況下將 ?? 直接與 && 或 || 混用。
// ❌ 語法錯誤 (SyntaxError)
const value = a && b ?? c;
// ✅ 必須明確指定優先順序
const value = (a && b) ?? c;
這是因為引擎不想幫你猜測你想先算哪一個,透過強制加括號,你的程式碼意圖會變得更明確。
2. ?. 對於「未宣告變數」無能為力
?. 只能檢查物件的「屬性」是否為空,但它無法檢查「變數本身」是否存在。
// 假設某個變數完全沒有用 const/let 宣告過
console.log(missingVar?.prop); // ❌ ReferenceError: missingVar is not defined
這再次提醒我們,這些語法是用來處理「資料結構的不確定性」,而不是用來修復「程式碼邏輯的錯誤」。
總結與銜接
掌握了 Optional Chaining 與 Nullish Coalescing,你就擁有了一套強大的「資料防護罩」。它們不只是讓程式碼變短,更重要的是它們體現了**宣告式編程(Declarative Programming)**的精神:你是在描述「我想要什麼樣的資料結果」,而不是在寫「如何一步步檢查資料是否存在」。
這與 React 的哲學不謀而合。React 讓我們專注於 UI 的狀態,而這些現代語法讓我們能安全、穩定地從複雜的資料流中提取出這些狀態。
核心要點回顧
?.(Optional Chaining) 解決了層層檢查null/undefined的痛苦,透過「短路」機制防止崩潰。??(Nullish Coalescing) 修正了||會誤判0與""的問題,提供更精準的預設值設定。- 兩者結合是 React 處理 API 資料與可選 Props 的標準做法。
下一步預告
現在我們已經學會了如何模組化組織程式碼(ESM),也掌握了如何安全地存取深層資料。接下來,我們要進入 Topic 5 的最後一個高峰:「函數式陣列操作(map, filter, reduce)」。
當你利用 data?.list ?? [] 安全地拿到一組陣列後,該如何利用這些函數式工具將資料轉換為 JSX 列表?這將是將 JS 邏輯轉化為 React 畫面最關鍵的最後一哩路。我們下一節見!